// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include <cstdlib>
#include <memory>
#include <string>
#include <vector>

#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class BaseRestAuth {
 public:
  virtual ~BaseRestAuth() = default;
  virtual AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) = 0;

  virtual AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) = 0;

  virtual AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) = 0;

  virtual AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) = 0;
};

class RoundRobinRestAuth {
 public:
  BaseRestAuth* GetRandom() const {
    if (auths_.size() > 1) {
      return auths_[rand() % auths_.size()].get();
    } else if (auths_.size() == 1) {
      return auths_[0].get();
    } else {
      return nullptr;
    }
  }

  BaseRestAuth* GetRandomTryNotUseFirst() const {
    if (auths_.size() > 2) {
      return auths_[1 + (rand() % (auths_.size() - 1))].get();
    } else if (auths_.size() == 2) {
      return auths_[1].get();
    } else {
      return GetRandom();
    }
  }

  BaseRestAuth* GetFirst() const { return auths_[0].get(); }

  int GetSize() const { return auths_.size(); }
  BaseRestAuth* GetNth(int n) const {
    return ((n >= 0) && (n < auths_.size())) ? auths_[n].get() : nullptr;
  }

  void Add(BaseRestAuth* auth) { auths_.emplace_back(auth); }

  template <typename TAuth, typename TConfig>
  void Construct(const TConfig& config) {
    if (config.has_rest_host_port() && config.has_rest_disable_ssl()) {
      CHECK_THROW(config.rest_host_port() == 80 || !config.rest_disable_ssl())
          << "Set port to 80 if you want to disable ssl. ";
    }

    std::vector<std::string> keys;
    if (config.has_key_filepath()) {
      keys.push_back(config.key_filepath());
    }
    if (config.key_filepaths_size()) {
      for (const std::string& p : config.key_filepaths()) {
        keys.push_back(p);
      }
    }

    std::vector<std::tuple<std::string, int32_t, std::string>> bind_ip_conns;
    bind_ip_conns.push_back(
        std::make_tuple(config.rest_host(), config.rest_host_port(), config.rest_bind_ip()));
    if (config.bind_ip_conns_size()) {
      for (const auto& ele : config.bind_ip_conns()) {
        bind_ip_conns.push_back(std::make_tuple(ele.host(), ele.port(), ele.bind_ip()));
      }
    }

    for (const std::string& key : keys) {
      for (const auto& ele : bind_ip_conns) {
        Add(new TAuth(key, std::get<0>(ele), std::get<1>(ele), std::get<2>(ele)));
      }
    }
  }

 protected:
  std::vector<std::unique_ptr<BaseRestAuth>> auths_;
};
